package web

import (
	"context"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/glennliao/apijson-go"
	"github.com/glennliao/apijson-go/consts"
	"github.com/glennliao/apijson-go/model"
	"github.com/gogf/gf/v2/container/gmap"
	"github.com/gogf/gf/v2/errors/gcode"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/iancoleman/orderedmap"
)

type GF框架 struct {
	apijson *apijson.APIJSON
	X响应处理器  func(处理器 func(ctx context.Context, 请求 model.Map) (响应 model.Map, 错误 error), 模式 M模式, 调试 bool) func(请求 *ghttp.Request)
}

func X新建GF(a *apijson.APIJSON) *GF框架 {
	return &GF框架{
		apijson: a,
		X响应处理器:  T通用响应,
	}
}

func (gf *GF框架) Y运行(服务器 ...*ghttp.Server) {
	var 服务 *ghttp.Server

	if len(服务器) == 0 {
		服务 = g.Server("apijson")
	} else {
		服务 = 服务器[0]
	}

	服务.Group("/", func(路由组 *ghttp.RouterGroup) {
		gf.B绑定路由(路由组)
	})

	服务.Run()
}

func (gf *GF框架) B绑定路由(路由组 *ghttp.RouterGroup, 模式 ...M模式) {
	if len(模式) == 0 {
		模式 = []M模式{S数据模式}
	}
	路由组.POST("/get", gf.X响应处理器(gf.H获取, 模式[0], gf.apijson.T调试模式))
	路由组.POST("/post", gf.X响应处理器(gf.T提交, 模式[0], gf.apijson.T调试模式))
	路由组.POST("/head", gf.X响应处理器(gf.T头部, 模式[0], gf.apijson.T调试模式))
	路由组.POST("/put", gf.X响应处理器(gf.G更新, 模式[0], gf.apijson.T调试模式))
	路由组.POST("/delete", gf.X响应处理器(gf.S删除, 模式[0], gf.apijson.T调试模式))
}

func (gf *GF框架) H获取(ctx context.Context, 请求 model.Map) (响应 model.Map, 错误 error) {
	查询 := gf.apijson.X新建查询(ctx, 请求)
	return 查询.H获取结果()
}

func (gf *GF框架) T头部(ctx context.Context, 请求 model.Map) (响应 model.Map, 错误 error) {
	return nil, 错误
}

func (gf *GF框架) T提交(ctx context.Context, 请求 model.Map) (响应 model.Map, 错误 error) {
	动作 := gf.apijson.X新建动作(ctx, http.MethodPost, 请求)
	return 动作.J结果()
}

func (gf *GF框架) G更新(ctx context.Context, 请求 model.Map) (响应 model.Map, 错误 error) {
	动作 := gf.apijson.X新建动作(ctx, http.MethodPut, 请求)
	return 动作.J结果()
}

func (gf *GF框架) S删除(ctx context.Context, 请求 model.Map) (响应 model.Map, 错误 error) {
	动作 := gf.apijson.X新建动作(ctx, http.MethodDelete, 请求)
	return 动作.J结果()
}

// 调试模式开启时, 使用有序map输出结果
func P排序映射(ctx context.Context, 请求体 []byte, 结果 *gmap.ListMap, 返回值 model.Map) *orderedmap.OrderedMap {
	请求排序映射 := orderedmap.New()
	错误 := 请求排序映射.UnmarshalJSON(请求体)
	if 错误 != nil {
		g.Log().Warning(ctx, 错误)
	}

	for _, 键 := range 请求排序映射.Keys() {
		if strings.HasPrefix(键, consts.Y引用键后缀) {
			continue
		}
		if 键 == consts.T标签 {
			continue
		}

		if strings.HasSuffix(键, consts.Y引用键后缀) {
			键 = 键[:len(键)-1]
		}

		结果.Set(键, 返回值[键])
	}

	return 请求排序映射
}

func C尝试执行(ctx context.Context, 执行函数 func(ctx context.Context) error) (错误 error) {
	defer func() {
		if 异常 := recover(); 异常 != nil {
			if 值, 存在 := 异常.(error); 存在 && gerror.HasStack(值) {
				错误 = 值
			} else {
				错误 = gerror.Newf(`%+v`, 异常)
			}
		}
	}()
	错误 = 执行函数(ctx)
	return
}

type C错误接口 interface {
	Code() int
	Error() string
}

func T通用响应(C处理器 func(ctx context.Context, 请求 model.Map) (响应 model.Map, 错误 error), 模式 M模式, 调试 bool) func(请求 *ghttp.Request) {
	return func(请求 *ghttp.Request) {
		元数据 := &gmap.ListMap{}
		状态码 := 200
		消息 := "success"
		节点结果 := &gmap.ListMap{}

		错误 := C尝试执行(请求.Context(), func(ctx context.Context) (错误 error) {

			返回值, 错误 := C处理器(ctx, 请求.GetMap())

			if 调试 {
				P排序映射(ctx, 请求.GetBody(), 元数据, 返回值)
			} else {
				for 键, 值 := range 返回值 {
					节点结果.Set(键, 值)
				}
			}
			return
		})

		if 错误 != nil {

			if 错误实例, 存在 := 错误.(C错误接口); 存在 {
				状态码 = 错误实例.Code()
				if strconv.Itoa(错误实例.Code())[0] == '4' {
					状态码 = 错误实例.Code()
					消息 = 错误实例.Error()
				} else {
					状态码 = 500
					消息 = "系统异常"
				}
			} else {

				if 错误实例, 存在 := 错误.(*gerror.Error); 存在 {
					if 错误实例.Code() == gcode.CodeInvalidParameter {
						状态码 = 400
						消息 = 错误实例.Error()
					} else {
						状态码 = 500
						消息 = "系统异常"
					}
				} else {
					状态码 = 500
					消息 = "系统异常"
				}
			}

			if 状态码 >= 500 {
				g.Log().Stack(false).Errorf(请求.Context(), "%+v", 错误)
			}

		}

		元数据.Set("ok", 状态码 == 200)
		元数据.Set("code", 状态码)
		元数据.Set("msg", 消息)
		元数据.Set("span", fmt.Sprintf("%s", time.Since(time.UnixMilli(请求.EnterTime))))

		响应 := 模式(节点结果, 元数据)
		请求.Response.WriteJson(响应.String())
	}
}
